﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using IdeaNMR.Shared.ScriptingEngine;
using IdeaNMR.Shared.Utils;

namespace IdeaNMR.Shared
{
    public class ExperimentBuilder : IExperimentBuilder
    {
        #region IExperimentBuilder Members
        public string SpectrometerScript { get; set; }
        public string SampleScript { get; set; }
        public string PulseProgramScript { get; set; }
        public string PulseProgramContent { get; set; }

        /// <summary>
        /// A valid filename not longer than 8 chars will be created 
        /// from whatever string you assign to it.
        /// </summary>
        public string PulseProgramFileName
        {
            get
            { return this._pulseProgramFileName; }
            set
            {
                var ifchars = System.IO.Path.GetInvalidFileNameChars();
                string res = value.Replace(" ", string.Empty);
                foreach (char c in ifchars) res = res.Replace(c.ToString(), string.Empty);
                this._pulseProgramFileName = res.Length > 8 ? res.Substring(0, 8) : res;
            }
        }
        private string _pulseProgramFileName;

        public Dictionary<string, string> Parameters { get; set; }

        public int ComponentsNumber
        {
            get { return this._Components.Count; }
        }
        public Dictionary<string, ComponentItem> Components
        {
            get { return this._Components; }
        }

        public LogBook<string> Log
        { get; private set; }

        // ALISTAIR: comments added on 10/07/2011
        // Generate the experiment macro, using the experiment name input by the user
        // and the username as input parameters
        public string CreateExperimentScript(string name, string username)
        {
            Log.Clear();
            Log.DefaultLevel = 0;
            Log.AddEntry("Starting Generating Experiments Script", LogBookEntryType.Checkpoint);

            #region Preparation of PythonInterpreter
            PythonInterpreter interpreter = new PythonInterpreter();
            // create a new "SetupScope" with the name "ExpScope"
            // i.e. the prefix used for commands in the pulse program
            // setup script defined by the user in the "Script" tab
            string experimentScopeName = "ExpScope";
            SetupScope expScope = new SetupScope(experimentScopeName);

            // copy the experimental parameters from ExperimentBuilder
            // to SetupScope parameters (as a dictionary of key/value pairs)
            expScope.Parameters = this.Parameters;

            interpreter.AddVariable("ScopeFunctions", new PythonScopeFunctions());
            interpreter.AddVariable(experimentScopeName, expScope.GetSecureScope());
            #endregion

#if DEBUG
            StringBuilder totalInstructions = new StringBuilder();
#endif

            #region Process Spectrometer
            Log.AddEntry("Processing Spectrometer.", LogBookEntryType.Checkpoint);
            if (string.IsNullOrEmpty(this.SpectrometerScript))
            {
                Log.AddEntry("Spectrometer script is null or empty.", LogBookEntryType.Error);
            }
            else
            {
#if DEBUG
                totalInstructions.Append(SpectrometerScript);
#endif
                // execute script to interpret the spectrometer
                ExecuteCode(interpreter, SpectrometerScript);
            }
            #endregion

            #region Process Sample
            Log.AddEntry("Processing Sample.", LogBookEntryType.Checkpoint);
            if (string.IsNullOrEmpty(this.SampleScript))
            {
                Log.AddEntry("Sample script is null or empty.", LogBookEntryType.Error);
            }
            else
            {
#if DEBUG
                totalInstructions.Append(SampleScript);
#endif
                // execute script to interpret the sample
                ExecuteCode(interpreter, SampleScript);
            }
            #endregion

            #region Processing Components

            /// This adds only ComponentExceptions to the ErrorList
            Log.AddEntry("Traversing through all components.", LogBookEntryType.General);
            Log.DefaultLevel++;
            foreach (ComponentItem cI in this._Components.Values)
            {
                string cScript = null;

                /// If component is not includable
                /// Check that content is good and add it to expScope
                if (!cI.IsIncludable)
                {
                    if (string.IsNullOrEmpty(cI.Content))
                    {
                        string msg = string.Format("The content of not includable component {0} is null or empty.", cI.Identifier);
                        Log.AddEntry(msg, LogBookEntryType.Warning);
                        continue;
                    }
                    expScope.AddComponent(cI.Identifier, cI.Name, cI.Content);
                    cScript = WrapComponentScript(cI.Script, cI.Identifier);
                }
                else
                {
                    cScript = cI.Script;
                }

#if DEBUG
                totalInstructions.Append(cScript);
#endif
                // execute script to interpret the "components"
                ExecuteCode(interpreter, cScript);
            }
            Log.DefaultLevel--;
            #endregion

            #region Processing Setup
            Log.AddEntry("Processing Pulse Program.", LogBookEntryType.Checkpoint);
            if (string.IsNullOrEmpty(this.PulseProgramScript))
            {
                Log.AddEntry("Pulse Program script is null or empty.", LogBookEntryType.Error);
            }
            else
            {
#if DEBUG
                totalInstructions.Append(this.PulseProgramScript);
#endif
                expScope.AddComponent("PulseProgram", this.PulseProgramFileName, this.PulseProgramContent);
                // execute script to interpret the pulse program
                ExecuteCode(interpreter, this.PulseProgramScript);
            }
            #endregion

#if DEBUG
            //string allInstructionsPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\totalInstructions.py";
            //System.IO.File.WriteAllText(allInstructionsPath, totalInstructions.ToString());

            //string ExpScopePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ExpScope.xml";
            //System.IO.File.WriteAllText(ExpScopePath, expScope.ToString());
#endif
            if (Log.HasErrors) return null;

            expScope = SetupScope.OpenSecuredSetupScope(
                            (SecuredSetupScope)interpreter.GetVariable(experimentScopeName));

            try
            {
                string result = expScope.CreateScript(name, username, this.PulseProgramFileName);
                return result;
            }
            catch (ParameterException ex)
            {
                string msg = string.Format("In function {0} parameter {1} not found.", ex.SourceFunctionName, ex.ParameterName);
                Log.AddEntry(msg, LogBookEntryType.Error);
            }
            return null;
        } // end CreateExperimentScript

        #endregion

        #region Constructors
        public ExperimentBuilder()
        {
            _Components = new Dictionary<string, ComponentItem>();

            this.Log = new LogBook<string>();
            //  this.Log = new Queue<LogBookEntry>();
        }
        #endregion

        #region Private
        private Dictionary<string, ComponentItem> _Components;

        private string WrapComponentScript(string script, string name)
        {
            string indentedScript = script.ToLocalNewLine().Replace(Environment.NewLine, Environment.NewLine + "    ");
            return string.Format("def func_{0}():\n    ComponentName=\"components.{0}\"\n{1}\nfunc_{0}()\n\n", name, indentedScript).ToLocalNewLine();
        }

        // ALISTAIR: comments added 10/07/2011
        // Execute the pyhton script provided as string parameter
        private void ExecuteCode(PythonInterpreter interpreter, string code)
        {
            //LogBookEntry error = new LogBookEntry();
            //error.Stage = stage;
            //error.EntryType = LogBookEntryType.Error;
            string msg = null;
            LogBookEntryType lbet = LogBookEntryType.Error;
            try
            {
                // evaluate the given code
                interpreter.Evaluate(code);
                lbet = LogBookEntryType.Status;
                msg = "Code executed correctly.";
            }
            catch (ParameterNotFoundException ex)
            {
                msg = string.Format("In function {0} parameter {1} not found.", ex.SourceFunctionName, ex.ParameterName);
            }
            catch (ParameterNullException ex)
            {
                msg = string.Format("In function {0} parameter is null or empty.", ex.SourceFunctionName, ex.ParameterName);
            }
            catch (ParameterException ex)
            {
                msg = string.Format("In function {0}; bad parameter {1}. {2}.", ex.SourceFunctionName, ex.ParameterName, ex.Message);
            }
            catch (PythonSyntaxErrorException ex)
            {
                msg = string.Format("Syntax error.\n{0}.", ex.Message);
            }
            catch (PythonRuntimeException ex)
            {
                msg = string.Format("Python runtime error.\n{0}", ex.Message);
            }
            catch (ApplicationException ex)
            {
                msg = ex.ToString();
                throw ex;
            }

            catch (Exception ex)
            {
                msg = ex.ToString();
                throw ex;
            }
            finally
            {
                Log.AddEntry(msg, lbet);
            }
        }
        #endregion

    } // end ExperimentBuilder class

} // end IdeaNMR.Shared namespace

